package com.arpa.wms.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.arpa.ntocc.common.common.cache.PartyCache;
import com.arpa.ntocc.common.common.constant.StrConsts;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.common.util.OrderIdGeneratorUtil;
import com.arpa.wms.domain.consts.NumConst;
import com.arpa.wms.domain.consts.PropertyConst;
import com.arpa.wms.domain.consts.TipConst;
import com.arpa.wms.domain.dto.InventoryOperationDto;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import static com.arpa.wms.domain.consts.NumConst.NUM_ONE;
import static com.arpa.wms.domain.consts.NumConst.NUM_ZERO;
import static java.util.stream.Collectors.toList;

/**
 * 波次拣货确认
 *
 * @author leo
 */
@Service
@Log4j2
public class WavePickingConfirmationService {

    @Resource
    public PartyCache partyCache;
    @Resource
    public RedissonClient redissonClient;
    @Resource
    private IPickingService iPickingService;
    @Resource
    private IPickingRegisterService iPickingRegisterService;
    @Resource
    private IPickingItemService iPickingItemService;
    @Resource
    private IOutboundService outboundService;
    @Resource
    private IOutboundItemService iOutboundItemService;
    @Resource
    private IOutboundRegisterService iOutboundRegisterService;
    @Resource
    private OutCheckService outCheckService;
    @Resource
    private IDeliveryDistributionService iDeliveryDistributionService;
    @Resource
    private IDeliveryItemService iDeliveryItemService;
    @Resource
    private IDeliveryService iDeliveryService;
    @Resource
    private IWmsTaskService iWmsTaskService;
    @Resource
    private IInventoryService iInventoryService;
    @Resource
    @Lazy
    private IPrepackagedItemService prepackagedItemService;
    //@Resource
    //private ILocationService iLocationService;
    //@Resource
    //private InventoryOperationService inventoryOperationService;
    //@Resource
    //private IInventoryRecordService iInventoryRecordService;


    /*************************************波次拣货确认更改生成出库单逻辑BEGIN**********************************************/

    /**
     * PC端批量确认拣货单
     *
     * @param picking 拣货单信息
     * @param isStockout 是否缺货登记 true是，false否
     */
    @Transactional(rollbackFor = {Exception.class})
    public void pickConfirmation(Picking picking,boolean isStockout) {
        String groupCode = picking.getGroupCode();
        //log.info("PC拣货单拣货确认，拣货单号：{}", pickingCode);
        //// 判断拣货单及状态
        //Picking picking = iPickingService.getOne(Wrappers.lambdaQuery(Picking.class)
        //        .select(Picking::getStatus, Picking::getWavePickingCode)
        //        .eq(Picking::getCode, pickingCode));
        log.info("PC拣货单拣货确认，拣货单：{}", JSONUtil.toJsonStr(picking));
        if (picking == null) {
            log.error("PC拣货单拣货确认失败。拣货单不存在。");
            throw new ServiceException("拣货单不存在，请确认。");
        }
        // 不是缺货登记触发拣货完成，“待拣货”、“拣货中”状态可以拣货确认，其他状态不允许确认
        if (!isStockout) {
            if (!StrUtil.equalsAny(picking.getStatus(), PickingStatusEnum.NOT_PICK.getValue(), PickingStatusEnum.PICKING.getValue())) {
                log.error("拣货单已完成，无法再拣货，请确认。拣货单:{}", JSONUtil.toJsonStr(picking));
                throw new ServiceException("拣货单已完成，无法再拣货，请确认。");
            }
        }
        String pickingCode = picking.getCode();
        String wavePickingCode = picking.getWavePickingCode();
        //更新未拣货的商品拣货登记数量
        boolean flag = iPickingRegisterService.update(new UpdateWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getGroupCode,groupCode)
                .eq(PickingRegister::getStatus, PickingStatusEnum.NOT_PICK.getValue())
                .eq(PickingRegister::getWavePickingCode, wavePickingCode)
                .setSql("picking_quantity=distribution_quantity-cancel_quantity")
                .set(PickingRegister::getStatus, PickingStatusEnum.FINISH_PICK.getValue()));
        log.info("PC拣货单拣货确认，更新拣货登记数量等于分配数量，状态为拣货完成，拣货单号：{}，波次号：{},结果：{}",pickingCode,wavePickingCode,flag);
        //更新拣货明细数量
        flag = iPickingItemService.update(new UpdateWrapper<PickingItem>().lambda()
                .eq(PickingItem::getGroupCode,groupCode)
                .eq(PickingItem::getStatus, PickingStatusEnum.NOT_PICK.getValue())
                .eq(PickingItem::getWavePickingCode, wavePickingCode)
                .setSql("picking_quantity=distribution_quantity-cancel_quantity")
                .set(PickingItem::getStatus, PickingStatusEnum.FINISH_PICK.getValue()));
        log.info("PC拣货单拣货确认，更新拣货明细拣货数量等于分配数量，状态为拣货完成，拣货单号：{}，波次号：{},结果：{}",pickingCode,wavePickingCode,flag);
        //更新拣货单拣货确认完成
        int updateCheckFinshCount = this.iPickingService.updateCheckFinsh(pickingCode,groupCode);
        log.info("PC拣货单拣货确认，更新拣货单拣货数量为计划数量-退货数量，状态为拣货完成，拣货单号：{}，波次号：{},结果：{}",pickingCode,wavePickingCode,updateCheckFinshCount);
        // 更新相关的任务，将任务设为完成
        int endTaskCount = iWmsTaskService.endTaskYb(pickingCode, picking.getModifiedBy(), picking.getModifiedName(),groupCode);
        log.info("PC拣货单拣货确认，结束拣货任务，拣货单号：{}，波次号：{},结果：{}",pickingCode,wavePickingCode,endTaskCount);
    }


    /**
     * 波次拣货确认
     *
     * @param pickingCode 拣货单号
     * @param isStockout
     * @return java.util.concurrent.Future<java.lang.Boolean>
     * @author liuxr
     * @date 2021/5/27 19:32
     */
    @Async
    public Future<Set<String>> wavePicking(String pickingCode,boolean isStockout,String groupCode) {
        RLock l = null;
        Set<String> cancelCodes = new HashSet<>();
        try {
            l = redissonClient.getLock(getPickingCheckLockKey(pickingCode));
            if (l.tryLock()) {
                log.info("波次拣货确认:拣货单【{}】，获得锁成功", pickingCode);
                Picking picking = iPickingService.getOne(Wrappers.lambdaQuery(Picking.class)
                        .select(Picking::getCode,
                                Picking::getStatus,
                                Picking::getWavePickingCode,
                                Picking::getPrepackagedCode,
                                Picking::getReview,
                                Picking::getGoodsSortQuantity,
                                Picking::getModifiedBy,
                                Picking::getModifiedName,
                                Picking::getGroupCode)
                        .eq(Picking::getGroupCode,groupCode)
                        .eq(Picking::getCode, pickingCode));
                String wavePickingCode = picking.getWavePickingCode();
                //校验打包数量是否一致，一致即可拣货确认
                log.info("波次拣货确认:拣货单【{}】，预打包校验",pickingCode);
                validatePickingPackageQuantity(picking);
                log.info("波次拣货确认:【step1】拣货单【{}】，拣货确认",pickingCode);
                this.pickConfirmation(picking,isStockout);
                log.info("波次拣货确认:【step2】拣货单【{}】，自动更新订单拣货数量及待复核状态",pickingCode);
                this.autoUpdateDeliveryPickingQuantity(pickingCode,wavePickingCode,groupCode);
                //log.info("波次拣货确认:【step3】拣货单【{}】，处理取消订单",pickingCode);
                //  cancelCodes = outCheckService.handleDeliveryCancelByWavePickingCode(wavePickingCode);
                log.info("波次拣货确认:【step4】拣货单【{}】，生成出库单",pickingCode);
                this.genOutboundInfoByDelivery(picking, picking.getReview());
                if (picking.getReview() == 0) {
                    log.info("波次拣货确认:【step5】拣货单【{}】，拣货单跳过复核",pickingCode);
                    log.info("波次拣货确认:【step5-1】拣货单【{}】，自动复核",pickingCode);
                    outCheckService.autoReview(wavePickingCode,groupCode);
                    log.info("波次拣货确认:【step5-2】拣货单【{}】，自动出库",pickingCode);
                    outCheckService.autoOutConfirmation(wavePickingCode,StrUtil.EMPTY, NUM_ZERO,NUM_ONE,groupCode);
                    log.info("波次拣货确认:【step6】拣货单【{}】出库单复核，异步释放容器",pickingCode);
                    outCheckService.releaseContainer(wavePickingCode, groupCode);
                }
            } else {
                log.error("波次拣货确认:拣货单【{}】，拣货确认获得锁失败", pickingCode);
                return new AsyncResult<Set<String>>(null);
            }
        } catch (Exception e) {
            log.error(StrConsts.CATCH_MESSAGE, e);
            throw new ServiceException(e.getMessage());
        } finally {
            try {
                if (null != l && l.isLocked()) {
                    l.unlock();
                }
            } catch (Exception e) {
                log.error("波次拣货确认:释放锁失败！！", e);
            }
        }

        return new AsyncResult<Set<String>>(cancelCodes);

    }

    /**
     * 校验打包数量是否一致，一致即可拣货确认
     * @author liuxr
     * @date 2021/11/24 11:23
     * @param picking 拣货单  （Picking::getCode,Picking::getStatus, Picking::getWavePickingCode,Picking::getPrepackagedCode）
     */
    private void validatePickingPackageQuantity(Picking picking) {
        String groupCode = picking.getGroupCode();
        //log.info("拣货单确认预打包校验，拣货单号：{}",picking);
        //Picking picking = iPickingService.getOne(new LambdaQueryWrapper<Picking>()
        //        .select(Picking::getPrepackagedCode)
        //        .eq(Picking::getCode, pickingCode));
        log.info("拣货单确认预打包校验，拣货单信息：{}",JSONUtil.toJsonStr(picking));
        if (picking == null) {
            log.error("拣货单确认预打包校验失败。拣货单不存在");
            throw new ServiceException("拣货单不存在，请确认。");
        }
        String prepackagedCode = picking.getPrepackagedCode();
        String pickingCode = picking.getCode();
        //判断是否是预打包分配的库存,如果是，则判断包裹数是否一致
        if (StrUtil.isNotBlank(prepackagedCode)) {
            List<PickingItem> pickingItems = iPickingItemService.list(new LambdaQueryWrapper<PickingItem>().eq(PickingItem::getGroupCode,groupCode)
                    .eq(PickingItem::getPickingCode, pickingCode).eq(PickingItem::getStatus,PickingStatusEnum.FINISH_PICK.getValue()));
            List<PrepackagedItem> prepackagedItemList = prepackagedItemService.list(new LambdaQueryWrapper<PrepackagedItem>()
                    .eq(PrepackagedItem::getPrepackagedCode, prepackagedCode)
                    .eq(PrepackagedItem::getGroupCode, groupCode));
            Map<String,BigDecimal> mapList = prepackagedItemList.stream().collect(Collectors.toMap(PrepackagedItem::getGoodCode, PrepackagedItem::getCombinationQuantity));
            if (IterUtil.isEmpty(pickingItems)) {
                //1.如果没有拣货，即没有进行缺货操作，直接拣货确认就相当于全捡
            } else {
                //2.部分拣货操作或者全缺
                List<PickingItem> notPickItems = iPickingItemService.list(new LambdaQueryWrapper<PickingItem>()
                        .eq(PickingItem::getGroupCode,groupCode)
                        .eq(PickingItem::getPickingCode, pickingCode)
                        .eq(PickingItem::getStatus, PickingStatusEnum.NOT_PICK.getValue()));
                notPickItems.forEach(info -> {
                    //2.1将未拣货的明细的拣货数量设置为分配数量
                    info.setPickingQuantity(info.getDistributionQuantity());
                    pickingItems.add(info);
                });
                Set<Integer> packedQuantitySet = pickingItems.stream().map(info -> {
                    BigDecimal tempPackedQuantity = info.getPickingQuantity().divide(MapUtil.get(mapList, info.getGoodCode(), BigDecimal.class));
                    boolean flag = judgeBigDecimalIsInteger(tempPackedQuantity);
                    if (!flag) {
                        throw new ServiceException("包裹数量不是整数，请重新拣货");
                    }
                    return (Integer) tempPackedQuantity.intValue();
                }).collect(Collectors.toSet());
                if (packedQuantitySet.size() > 1) {
                    throw new ServiceException("商品拣货数量超出已拣货商品可组成包裹数");
                }
            }
        }
    }

    /**
     * 波次拣货确认根据订单生成出库单信息
     *
     * @param picking 拣货单
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    public void genOutboundInfoByDelivery(Picking picking, int isReview) {
        String wavePickingCode = picking.getWavePickingCode();
        String groupCode = picking.getGroupCode();
        log.info("拣货完成生成出库单。波次号：{}", wavePickingCode);
        List<PickingRegister> pickingRegisters = iPickingRegisterService.list(
                Wrappers.lambdaQuery(PickingRegister.class)
                        .gt(PickingRegister::getDistributionQuantity, BigDecimal.ZERO)
                        .eq(PickingRegister::getWavePickingCode, wavePickingCode)
                        .eq(PickingRegister::getGroupCode,groupCode)
                        .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.FINISH_PICKING.getValue()));
        log.info("获取拣货单拣货登记明细：{}", JSONUtil.toJsonStr(pickingRegisters));
        // 如果拣货确认单为空，即所有商品都缺货登记了，那就不生成出库单
        if (pickingRegisters.isEmpty()) {
            // 拣货数量全为0，则不生成出库单，订单状态要改为待分配
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                    .eq(Delivery::getWavePickingCode, wavePickingCode)
                    .eq(Delivery::getGroupCode,groupCode)
                    .set(Delivery::getStatus, DeliveryStatusEnum.UN_DISTRIBUTION.getValue()));
        } else {
            //获取所有未作废的订单
            List<Delivery> deliveryList = iDeliveryService.list(Wrappers.lambdaQuery(Delivery.class)
                    .eq(Delivery::getWavePickingCode, wavePickingCode)
                    .eq(Delivery::getGroupCode,groupCode)
                    .ne(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
                    .eq(Delivery::getIsCancel, NUM_ZERO));

            //获取所有取消未作废的订单号
            List<String> deliveryCodes = deliveryList.parallelStream().map(Delivery::getCode).collect(toList());
            //生成出库单
            List<Outbound> outboundList = genOutboundByDelivery(deliveryList, picking, isReview);

            List<DeliveryItem> deliveryItemList = iDeliveryItemService.list(Wrappers.lambdaQuery(DeliveryItem.class)
                    .in(DeliveryItem::getDeliveryCode, deliveryCodes)
                    .eq(DeliveryItem::getGroupCode,groupCode));

            List<DeliveryDistribution> deliveryDistributionsList = iDeliveryDistributionService.list(Wrappers.lambdaQuery(DeliveryDistribution.class)
                    .in(DeliveryDistribution::getDeliveryCode, deliveryCodes)
                    .eq(DeliveryDistribution::getGroupCode,groupCode));

            //将所有的分配明细按订单号分组
            Map<String, List<DeliveryItem>> deliveryItemMap = deliveryItemList.stream().collect(Collectors.groupingBy(DeliveryItem::getDeliveryCode));

            //将所有的分配明细按订单号分组
            Map<String, List<DeliveryDistribution>> deliveryDistributionsMap = deliveryDistributionsList.stream().collect(Collectors.groupingBy(DeliveryDistribution::getDeliveryCode));


            //生成出库单明细，出去单确认明细
            Map<String, Object> itemAndRegister = genOutboundItemByDeliveryItem(outboundList, deliveryDistributionsMap, isReview);
            if (MapUtil.isEmpty(itemAndRegister)) {
                log.error("出库单保存失败，没有出库详细信息");
                throw new ServiceException("出库单保存失败；");
            }
            List<OutboundItem> outboundItems = MapUtil.get(itemAndRegister, "item", List.class);
            if (MapUtil.isEmpty(itemAndRegister)) {
                log.error("出库单保存失败，没有出库明细");
                throw new ServiceException("出库单保存失败；");
            }
            List<OutboundRegister> outboundRegisters = MapUtil.get(itemAndRegister, "register", List.class);
            if (MapUtil.isEmpty(itemAndRegister)) {
                log.error("出库单保存失败，没有出库登记明细");
                throw new ServiceException("出库单保存失败；");
            }
            // 保存出库单
            outboundService.saveBatch(outboundList);
            log.info("保存出库单。{}", JSONUtil.toJsonStr(outboundList));
            iOutboundItemService.saveBatch(outboundItems);
            log.info("保存出库单明细。{}", JSONUtil.toJsonStr(outboundItems));
            iOutboundRegisterService.saveBatch(outboundRegisters);
            log.info("保存出库登记明细。{}", JSONUtil.toJsonStr(outboundRegisters));
        }
    }


    /**
     * 波次拣货确认根据订单生成出库单
     *
     * @param deliveryList 订单列表
     * @param picking  拣货单信息
     * @param isReview     拣货单是否跳过复核  0 否，1 是
     * @return
     */
    private List<Outbound> genOutboundByDelivery(List<Delivery> deliveryList, Picking picking, int isReview) {
        String pickingCode = picking.getCode();
        List<String> codes = OrderIdGeneratorUtil.generatorOrderId(OrderIdGeneratorUtil.OrderTypeEnum.CK, deliveryList.size(),picking.getGroupCode());
        List<Outbound> outboundList = new ArrayList<>();
        int i = 0;
        for (Delivery r : deliveryList) {
            // 生成出库单
            Outbound outbound = new Outbound();
            BeanUtil.copyProperties(r,outbound);
            // 生成出库单号  CK+货主编码+yyyyMMdd+四位流水号
            outbound.setCode(codes.get(i));
            outbound.setWarehouseCode(r.getWarehouseCode());
            outbound.setShipmentCode(r.getShipmentCode());
            outbound.setShipmentName(r.getShipmentName());
            outbound.setCustomerCode(r.getCustomerCode());
            outbound.setGroupCode(r.getGroupCode());
            outbound.setIssue(0);
            outbound.setSourceCode(r.getSourceCode());
            outbound.setDeliveryCode(r.getCode());
            // 计划发货数量 = 拣货数量
            outbound.setPlanQuantity(r.getPickNum());
            outbound.setPickingCode(pickingCode);
            outbound.setType("0");
            outbound.setWavePickingCode(r.getWavePickingCode());
            outbound.setLogisticsCode(r.getLogisticsCode());
            outbound.setPickingCode(pickingCode);
            outbound.setDeleted(0);
            outbound.setCarrierCode(r.getCarrierCode());
            outbound.setCarrierName(r.getCarrierName());
            outbound.setOrderTag("5");
            outbound.setTemporaryCode(r.getTemporaryCode());
            if (isReview == 0) {
                //拣货单不需要复核，如果不复核直接创建复核单
                outbound.setStatus(OutboundStatusEnum.NOT_OUT.getValue());
                outbound.setRecheckQuantity(r.getPickNum());
            } else {
                outbound.setStatus(OutboundStatusEnum.NOT_RECHECK.getValue());
            }
            // 出库单中订单数量

            outbound.setDeliveryCount(NUM_ONE);
            outbound.setGoodsSortQuantity(picking.getGoodsSortQuantity());
            outbound.setReview(picking.getReview());

            //出库单中订单计划总数量
            outbound.setDeliveryPlanQuantity(r.getTotalNum());
            //是否是预打包单
            outbound.setPrepackaged(r.getPrepackaged());
            outbound.setPrepackagedCode(r.getPrepackagedCode());
            outbound.setGmtCreated(LocalDateTime.now());
            outbound.setGmtModified(LocalDateTime.now());
            outboundList.add(outbound);
            i++;
        }
        return outboundList;


    }

    /**
     * 波次拣货确认 构建出库单明细、待复核的出库确认单
     *
     * @param outboundList
     * @param deliveryDistributionsMap
     * @param isReview
     * @return
     */
    private Map<String, Object> genOutboundItemByDeliveryItem(List<Outbound> outboundList, Map<String,
            List<DeliveryDistribution>> deliveryDistributionsMap, int isReview) {

        List<OutboundItem> outboundItems = new ArrayList<>();
        List<OutboundRegister> outboundRegisters = new ArrayList<>();
        for (Outbound r : outboundList) {
            List<DeliveryDistribution> deliveryDistributions = deliveryDistributionsMap.get(r.getDeliveryCode());


            for (DeliveryDistribution i : deliveryDistributions) {
                OutboundItem outboundItem = new OutboundItem();
                outboundItem.setCode(IdUtil.simpleUUID());
                outboundItem.setOutboundCode(r.getCode());
                outboundItem.setDeliveryItemCode(i.getDeliveryItemCode());
                outboundItem.setDeliveryDistributionCode(i.getCode());
                outboundItem.setGroupCode(i.getGroupCode());
                outboundItem.setGoodsCode(i.getGoodsCode());
                outboundItem.setGoodCode(i.getGoodCode());
                outboundItem.setGoodsName(i.getGoodsName());
                outboundItem.setGoodsBarCode(i.getGoodsBarCode());
                outboundItem.setSpec(i.getSpec());
                outboundItem.setGoodsUnit(i.getGoodsUnit());
                outboundItem.setGoodsUnitName(i.getGoodsUnitName());
                outboundItem.setBasicUnit(i.getBasicUnit());
                outboundItem.setBasicUnitName(i.getBasicUnitName());
                outboundItem.setLot(i.getLot());
                outboundItem.setReceiveLot(i.getReceiveLot());
                outboundItem.setGoodsStatus(i.getGoodsStatus());
                outboundItem.setGoodsStatusName(i.getGoodsStatusName());
                outboundItem.setWarehouseCode(r.getWarehouseCode());
                outboundItem.setLocation(i.getLocation());
                outboundItem.setPlanQuantity(i.getPickingQuantity());
                outboundItem.setGmtManufacture(i.getGmtManufacture());
                outboundItem.setGmtExpire(i.getGmtExpire());
                outboundItem.setGmtStock(i.getGmtStock());
                outboundItem.setTemporaryCode(r.getTemporaryCode());
                outboundItem.setWavePickingCode(i.getWavePickingCode());
                outboundItem.setInventoryCode(i.getInventoryCode());
                outboundItem.setOldInventoryCode(i.getOldInventoryCode());
                outboundItem.setSupplier(i.getSupplier());
                //基本单位转换数量默认为1
                outboundItem.setBasicQuantity(BigDecimal.ONE);
                if (isReview == 0) {
                    outboundItem.setRecheckQuantity(i.getPickingQuantity());
                    outboundItem.setStatus(OutboundRecheckStatusEnum.RECHECKED.getValue());
                } else {
                    outboundItem.setStatus(OutboundRecheckStatusEnum.NOT_RECHECK.getValue());
                }
                // 出库数量初始值为0
                outboundItem.setOutboundQuantity(BigDecimal.ZERO);
                outboundItem.setDeleted(0);
                outboundItem.setTaskCode(StrUtil.EMPTY);
                outboundItem.setOperatorBy(StrUtil.EMPTY);
                outboundItem.setOperatorName(StrUtil.EMPTY);
                outboundItem.setCreatedBy(StrUtil.EMPTY);
                outboundItem.setOrderNum(i.getOrderNum());
                outboundItem.setInventoryContainerBarCode(i.getInventoryContainerBarCode());
                outboundItems.add(outboundItem);


                // 生成一条待复核的出库复核单
                OutboundRegister outboundRegister = new OutboundRegister();
                outboundRegister.setCode(IdUtil.simpleUUID());
                outboundRegister.setWavePickingCode(r.getWavePickingCode());
                outboundRegister.setWarehouseCode(r.getWarehouseCode());
                outboundRegister.setOutboundCode(r.getCode());
                outboundRegister.setOutboundItemCode(outboundItem.getCode());
                outboundRegister.setDeliveryDistributionCode(i.getCode());
                outboundRegister.setDeliveryItemCode(i.getDeliveryItemCode());
                outboundRegister.setGroupCode(i.getGroupCode());
                //outboundRegister.setPickingRegisterCode(pickingRegister.getCode());
                outboundRegister.setGoodsCode(i.getGoodsCode());
                outboundRegister.setGoodCode(i.getGoodCode());
                outboundRegister.setGoodsName(i.getGoodsName());
                outboundRegister.setGoodsBarCode(i.getGoodsBarCode());
                outboundRegister.setSpec(i.getSpec());
                outboundRegister.setGoodsUnit(i.getGoodsUnit());
                outboundRegister.setGoodsUnitName(i.getGoodsUnitName());
                outboundRegister.setBasicUnit(i.getBasicUnit());
                outboundRegister.setBasicUnitName(i.getBasicUnitName());
                outboundRegister.setLot(i.getLot());
                outboundRegister.setReceiveLot(i.getReceiveLot());
                outboundRegister.setGoodsStatus(i.getGoodsStatus());
                outboundRegister.setGoodsStatusName(i.getGoodsStatusName());
                outboundRegister.setSerialNumber(i.getSerialNumber());
                outboundRegister.setSupplier(i.getSupplier());
                outboundRegister.setPlanQuantity(i.getPickingQuantity());
                outboundRegister.setExtendOne(i.getExtendOne());
                outboundRegister.setExtendTwo(i.getExtendTwo());
                outboundRegister.setExtendThree(i.getExtendThree());
                outboundRegister.setExtendFour(i.getExtendFour());
                outboundRegister.setExtendFive(i.getExtendFive());
                outboundRegister.setExtendSix(i.getExtendSix());
                if (isReview == 0) {
                    // 复核数量初始值为总待拣数量
                    outboundRegister.setRecheckQuantity(i.getPickingQuantity());
                    outboundRegister.setNotRecheckQuantity(BigDecimal.ZERO);
                    outboundRegister.setStatus(OutboundRecheckStatusEnum.RECHECKED.getValue());
                } else {
                    outboundRegister.setNotRecheckQuantity(i.getPickingQuantity());
                    outboundRegister.setStatus(OutboundRecheckStatusEnum.NOT_RECHECK.getValue());
                }
                outboundRegister.setDeleted(0);
                outboundRegister.setTaskCode(StrUtil.EMPTY);
                outboundRegister.setOperatorBy(StrUtil.EMPTY);
                outboundRegister.setOperatorName(StrUtil.EMPTY);
                outboundRegister.setCreatedBy(StrUtil.EMPTY);
                outboundRegister.setLocation(i.getLocation());
                outboundRegister.setGmtExpire(i.getGmtExpire());
                outboundRegister.setGmtStock(i.getGmtStock());
                outboundRegister.setInventoryCode(i.getInventoryCode());
                outboundRegister.setOldInventoryCode(i.getOldInventoryCode());
                outboundRegister.setInventoryContainerBarCode(i.getInventoryContainerBarCode());
                outboundRegisters.add(outboundRegister);
            }
        }


        Map<String, Object> map = new HashMap<>();
        map.put("item", outboundItems);
        map.put("register", outboundRegisters);
        return map;
    }

    /**
     * 波次拣货确认自动更新订单拣货数量
     *
     * @param pickingCode 拣货单号
     * @param wavePickingCode 波次号
     * @author liuxr
     * @date 2021/5/27 9:06
     */
    @Transactional(rollbackFor = {Exception.class})
    public void autoUpdateDeliveryPickingQuantity(String pickingCode,String wavePickingCode,String groupCode) {
        log.info("波次拣货确认：自动更新订单拣货数量【step1】。更新订单分配明细拣货数量，拣货单号：{}，波次号：{}",pickingCode,wavePickingCode);
        int count = iDeliveryDistributionService.updatePickingQuantityStatusByWavePickingCode(wavePickingCode, DeliveryStatusEnum.UN_RECHECK.getValue(),groupCode);
        log.info("波次拣货确认：自动更新订单拣货数量【step1】。更新订单分配明细拣货数量，拣货单号：{}，波次号：{}，结果：{}",pickingCode,wavePickingCode,count);

        //更新订单明细表拣货数量和状态
        log.info("波次拣货确认：自动更新订单拣货数量【step2】。更新订单明细拣货数量，拣货单号：{}，波次号：{}",pickingCode,wavePickingCode);
        count = iDeliveryItemService.updatePickingQuantityStatusByWavePickingCode(wavePickingCode, DeliveryStatusEnum.UN_RECHECK.getValue(),groupCode);
        log.info("波次拣货确认：自动更新订单拣货数量【step2】。更新订单明细拣货数量，拣货单号：{}，波次号：{}，结果：{}",pickingCode,wavePickingCode,count);


        log.info("波次拣货确认：自动更新订单拣货数量【step3】。更新订单拣货数量，拣货单号：{}，波次号：{}",pickingCode,wavePickingCode);
        count = iDeliveryService.updatePickingQuantityByWavePickingCode(wavePickingCode,groupCode);
        log.info("波次拣货确认：自动更新订单拣货数量【step3】。更新订单拣货数量，拣货单号：{}，波次号：{}，结果：{}",pickingCode,wavePickingCode,count);


        log.info("波次拣货确认：自动更新订单拣货数量【step4】。更新订单待复核状态，拣货单号：{}，波次号：{}",pickingCode,wavePickingCode);
        count = iDeliveryService.updateStatusWithRecheck(wavePickingCode,groupCode);
        log.info("波次拣货确认：自动更新订单拣货数量【step4】。更新订单待复核状态，拣货单号：{}，波次号：{}，结果：{}",pickingCode,wavePickingCode,count);
    }


    /**
     * 波次拣货缺货登记
     *
     * @param entities   订单分配明细
     * @param wavePickingCode 波次号
     * @param inventoryCode   库存标识
     * @return
     */
    public boolean wavePickingOutStockRegistration(List<DeliveryDistribution> entities, String wavePickingCode, String inventoryCode) {
        if (IterUtil.isEmpty(entities)) {
            throw new ServiceException("缺货登记信息为空！！！");
        }

        //判断缺货登记的状态是否已拣货，已拣货的不能进行缺货登记
        PickingRegister register = iPickingRegisterService.getOne(new QueryWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingRegister::getWavePickingCode, wavePickingCode)
                .eq(PickingRegister::getInventoryCode, inventoryCode));

        log.info("拣货缺货登记，拣货明细:{}", JSONUtil.toJsonStr(register));
        if (PickingStatusEnum.FINISH_PICK.getValue().equals(register.getStatus())) {
            throw new ServiceException(TipConst.THE_GOODS_HAS_PICKED_CAN_NOT_STOCK_OUT);
        }
        //过滤所有缺货数不为空且数量不等于0的分配明细。
        //List<DeliveryDistribution> entities = distributions.stream().
        //        filter(r -> null != r.getRefundQuantity() && r.getRefundQuantity().compareTo(BigDecimal.ZERO) != NumConst.NUM_ZERO).
        //        collect(toList());
        entities.forEach(r -> {
            if (null == r.getRefundQuantity()) {
                r.setRefundQuantity(BigDecimal.ZERO);
            }
        });

        //数量校验
        long count = entities.stream().filter(r -> r.getRefundQuantity().compareTo(BigDecimal.ZERO) < NumConst.NUM_ZERO).count();
        if (count > 0) {
            throw new ServiceException("缺货数量不能为负数！！！");
        }
        count = entities.stream().filter(r -> r.getRefundQuantity().compareTo(r.getDistributionQuantity()) > NumConst.NUM_ZERO).count();
        if (count > 0) {
            throw new ServiceException("缺货数量不能大于分配数量！！！");
        }
        //包裹数校验
        validatePackageQuantity(entities,register);
        // 尝试触发主单拣货完成逻辑
        String pickingCode = register.getPickingCode();
        //按订单分组
        Map<String, List<DeliveryDistribution>> distributionMap = entities.stream()
                .collect(Collectors.groupingBy(DeliveryDistribution::getDeliveryCode, Collectors.toList()));
        distributionMap.forEach((k, v) -> {
            for (DeliveryDistribution entity : v) {
                if (StringUtils.isBlank(entity.getCode())) {
                    throw new ServiceException(TipConst.CODE_NOT_EMPTY);
                }
                BigDecimal refundQuantity = entity.getRefundQuantity();
                //更新分配表、拣货单明细表，拣货单表
                updateItemsStockoutInfo(refundQuantity, entity.getCode());
            }
            //更新订单缺货数量、拣货数量、状态
            iDeliveryService.updateOutStockQuantity(k);
        });
        //更新拣货单缺货数量、拣货数量、未拣货数量、状态
        iPickingService.updatePickingByPickingRegister(wavePickingCode);
        //处理订单完全缺货
        iDeliveryDistributionService.handleWholeStockOutDelivery(wavePickingCode, PropertyConst.PICKING);
        //int finishPickingFlag = iPickingService.updateStatusIfFinish(pickingCode, PickingStatusEnum.FINISH_PICK.getValue());
        //if (finishPickingFlag == NumConst.NUM_ZERO) {
        //    // 如果整个单子没完成，则将任务设置为作业中
        //    iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode)
        //            .set(WmsTask::getJobStatus, WmsTaskJobStatusEnum.WORKING)
        //            .set(WmsTask::getOperatorBy, UserUtil.getCode())
        //            .set(WmsTask::getOperatorName, partyCache.translate(UserUtil.getCode())));
        //    // 更新主单为拣货中状态
        //    iPickingService.update(new UpdateWrapper<Picking>().lambda()
        //            .eq(Picking::getCode, pickingCode)
        //            .set(Picking::getStatus, PickingStatusEnum.PICKING.getValue()));
        //}

        //缺货登记执行到最后一条时，触发批量拣货确认操作
        List<String> registerStatuses = new ArrayList<>(Arrays.asList(PickingStatusEnum.NOT_PICK.getValue(), PickingStatusEnum.PICKING.getValue()));
        int unFinshCount = iPickingRegisterService.count(new QueryWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getWavePickingCode, wavePickingCode)
                .eq(PickingRegister::getGroupCode, UserUtil.getBranchCode())
                .in(PickingRegister::getStatus, registerStatuses)
                .eq(PickingRegister::getDeleted, NumConst.NUM_ZERO));
        if (unFinshCount > 0) {

            // 如果整个单子没完成，则将任务设置为作业中
            iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode).eq(WmsTask::getGroupCode, UserUtil.getBranchCode())
                    .set(WmsTask::getJobStatus, WmsTaskJobStatusEnum.WORKING)
                    .set(WmsTask::getOperatorBy, UserUtil.getCode())
                    .set(WmsTask::getOperatorName, partyCache.translate(UserUtil.getCode())));
            // 更新主单为拣货中状态
            iPickingService.update(new UpdateWrapper<Picking>().lambda()
                    .eq(Picking::getCode, pickingCode)
                    .eq(Picking::getGroupCode, UserUtil.getBranchCode())
                    .set(Picking::getStatus, PickingStatusEnum.PICKING.getValue()));
            return false;
        } else {
            return true;
        }
    }


    private void validatePackageQuantity(List<DeliveryDistribution> entities,PickingRegister pickingRegister){
        String branchCode = UserUtil.getBranchCode();
        Picking picking = iPickingService.getOne(new LambdaQueryWrapper<Picking>().eq(Picking::getCode, pickingRegister.getPickingCode()).eq(Picking::getGroupCode,branchCode));
        if (picking == null) {
            throw new ServiceException("拣货单不能为空，请确认");
        }
        //如果是预打包库存，则进行打包数量校验
        String prepackagedCode = picking.getPrepackagedCode();
        if (StrUtil.isNotEmpty(prepackagedCode)) {
            List<PrepackagedItem> prepackagedItemList = prepackagedItemService.list(new LambdaQueryWrapper<PrepackagedItem>()
                    .eq(PrepackagedItem::getPrepackagedCode, prepackagedCode)
                    .eq(PrepackagedItem::getGroupCode, branchCode));
            Map<String, BigDecimal> prepackagedItemMap = prepackagedItemList.stream().collect(Collectors.toMap(PrepackagedItem::getGoodCode, PrepackagedItem::getCombinationQuantity));
            //总的拣货数量=总的计划数量-总的缺货数量
            BigDecimal planQuantity = pickingRegister.getPlanQuantity();
            BigDecimal totalRefundQuantity = entities.stream().map(DeliveryDistribution::getRefundQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal totolPickingQuantity = planQuantity.subtract(totalRefundQuantity);
            BigDecimal combinationQuantity = MapUtil.get(prepackagedItemMap, pickingRegister.getGoodCode(), BigDecimal.class);
            BigDecimal packQuantity = totolPickingQuantity.divide(combinationQuantity, 6, BigDecimal.ROUND_HALF_UP);
            BigDecimal packQuantity2 = totolPickingQuantity.divide(combinationQuantity, 0, BigDecimal.ROUND_HALF_UP);
            if (packQuantity.compareTo(packQuantity2) != 0) {
                throw new ServiceException("打包数量必须为整数，请重新拣货");
            }
            //实际打包数量
            Integer actualPackQuantity = packQuantity.intValue();
            //查询已经缺货登记完的拣货登记信息
            List<PickingRegister> pickingRegisters = iPickingRegisterService.list(new LambdaQueryWrapper<PickingRegister>()
                    .eq(PickingRegister::getPickingCode, pickingRegister.getPickingCode())
                    .eq(PickingRegister::getGroupCode, branchCode));

            if (IterUtil.isNotEmpty(pickingRegisters)) {
                //取第一个拣货明细的打包数量作为已打包数量
                PickingRegister register = pickingRegisters.get(NUM_ZERO);
                Integer packedQuantity = register.getPickingQuantity().divide(MapUtil.get(prepackagedItemMap,register.getGoodCode(),BigDecimal.class)).intValue();
                if (actualPackQuantity > packedQuantity) {
                    throw new ServiceException("当前商品拣货数量超出已拣货商品可组成包裹数");
                }
                if (actualPackQuantity < packedQuantity) {
                    throw new ServiceException("当前商品拣货数量小于已拣货商品可组成包裹数，是否自动退拣");
                }
            }
        }


    }
    /**
     * 更新缺货信息
     *
     * @param refundQuantity           缺货数量
     * @param deliveryDistributionCode 订单分配明细code
     * @date 2021/4/19 10:59
     */
    @Transactional(rollbackFor = {Exception.class})
    public void updateItemsStockoutInfo(BigDecimal refundQuantity, String deliveryDistributionCode) {
        String branchCode = UserUtil.getBranchCode();
        DeliveryDistribution deliveryDistribution = iDeliveryDistributionService.getOne(Wrappers.lambdaQuery(DeliveryDistribution.class)
                .eq(DeliveryDistribution::getCode, deliveryDistributionCode));
        if (null == deliveryDistribution) {
            log.error("缺货登记，订单分配明细找不到：{}", deliveryDistributionCode);
            throw new ServiceException("订单分配明细找不到");
        }
        //拣货波次号
        String wavePickingCode = deliveryDistribution.getWavePickingCode();
        String inventoryCode = deliveryDistribution.getInventoryCode();
        String oldInventoryCode = deliveryDistribution.getOldInventoryCode();

        log.info("拣货单：{}，缺货登记，获取订单分配明细信息：{}", wavePickingCode, JSONUtil.toJsonStr(deliveryDistribution));

        //1、更新订单分配明细订单明细缺货数量、拣货数量、状态
        iDeliveryService.updateItemsOutStockQuantity(refundQuantity, deliveryDistributionCode,
                deliveryDistribution.getDeliveryItemCode());
        //2、更新拣货登记明细拣货明细缺货数量、拣货数量、状态
        iPickingService.updateItemsOutStockQuantity(wavePickingCode, oldInventoryCode, refundQuantity,branchCode);

        if (refundQuantity.compareTo(BigDecimal.ZERO) > 0) {
            //更新临时库存
            iInventoryService.stockoutInventory(inventoryCode, refundQuantity, StockoutTypeEnum.PICKING.getValue(), deliveryDistribution.getPickingCode());
        }
    }



    /*@Transactional(rollbackFor = Exception.class)
    public void handleDeliveryCancelByWavePickingCode(List<DeliveryDistribution> deliveryDistributions,String pickCode) {
        //通过分配明细获取所有临时库存标识
        Set<String> tempInventoryCodeSet = deliveryDistributions.stream().map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());
        //通过分配明细获取所有原始库存标识
        Set<String> oldInventoryCodeSet = deliveryDistributions.stream().map(DeliveryDistribution::getOldInventoryCode).collect(Collectors.toSet());
        tempInventoryCodeSet.addAll(oldInventoryCodeSet);
        //通过临时库存、原始库存获取所有库存信息
        List<Inventory> inventories = iInventoryService.list( Wrappers.lambdaQuery(Inventory.class).in(Inventory::getCode, tempInventoryCodeSet));
        //将库存信息通过库存code转成map
        Map<String, Inventory> inventoryMap = inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));

        Map<String, List<DeliveryDistribution>> listMap = deliveryDistributions.stream()
                .collect(Collectors.groupingBy(DeliveryDistribution::getInventoryCode));
        //要减少库存的临时库存总集合
        List<InventoryOperationDto> subtract = listMap.entrySet().stream()
                .map(m -> {
                    InventoryOperationDto dto = new InventoryOperationDto();
                    dto.setUuid(IdUtil.simpleUUID());
                    dto.setInventoryCode(m.getKey());
                    dto.setInventory(inventoryMap.get(m.getKey()));
                    dto.setQuantity(m.getValue()
                            .stream()
                            .map(DeliveryDistribution::getRefundQuantity)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                    return dto;

                })
                .collect(Collectors.toList());

        listMap = deliveryDistributions.stream()
                .collect(Collectors.groupingBy(DeliveryDistribution::getOldInventoryCode));
        //要增加库存的原始库存总集合
        List<InventoryOperationDto> add = listMap.entrySet().stream()
                .map(m -> {
                    InventoryOperationDto dto = new InventoryOperationDto();
                    dto.setUuid(IdUtil.simpleUUID());
                    dto.setInventoryCode(m.getKey());
                    dto.setInventory(inventoryMap.get(m.getKey()));
                    dto.setQuantity(m.getValue()
                            .stream()
                            .map(DeliveryDistribution::getRefundQuantity)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                    return dto;

                })
                .collect(Collectors.toList());
        //所有库存操作记录集合
        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        String groupCode = UserUtil.getBranchCode();
            //创建减库存操作记录
        subtract.forEach(r -> {
                //临时库存减库存操作记录
                InventoryRecord record = new InventoryRecord();
                BeanUtil.copyProperties(r.getInventory(), record);
                record.setCode(IdUtil.simpleUUID());
                record.setGroupCode(groupCode);
                record.setInventoryCode(r.getInventoryCode());
                record.setSourceCode(pickCode);
                record.setNum(r.getQuantity().negate());
                record.setType(InventoryRecordTypeEnum.PICK.getValue());
                inventoryRecords.add(record);

            });
        //创建减库存操作记录
        add.forEach(r -> {
                //退减库存擦插入操作记录
                InventoryRecord reRecord = new InventoryRecord();
                BeanUtil.copyProperties(r.getInventory(), reRecord);
                reRecord.setCode(IdUtil.simpleUUID());
                reRecord.setGroupCode(groupCode);
                reRecord.setInventoryCode(r.getInventoryCode());
                reRecord.setSourceCode(pickCode);
                reRecord.setNum(r.getQuantity());
                reRecord.setType(InventoryRecordTypeEnum.PICK.getValue());
                inventoryRecords.add(reRecord);
            });
        List<InventoryOperationDto> subtractData = getInventoryOperationDtos(subtract);
        List<InventoryOperationDto> addData = getInventoryOperationDtos(add);
        //减少临时库存
        inventoryOperationService.subtractBatch(subtractData);
        //批量加库存
        inventoryOperationService.addBatch(addData);
        //批量插入操作记录
        iInventoryRecordService.saveBatch(inventoryRecords);
    }*/


    /*************************************波次拣货确认更改生成出库单逻辑END************************************************/


    /**
     * 拣货确认分布式锁key
     *
     * @param code 拣货单号
     * @return
     */
    private String getPickingCheckLockKey(String code) {
        return String.format("picking-check-lock-%s", code);
    }

    /**
     * 拣货确认分布式锁key
     *
     * @param code 拣货单号
     * @return
     */
    private String getOutConfirmLockKey(String code) {
        return String.format("outConfirm-lock-%s", code);
    }

    @NotNull
    private List<InventoryOperationDto> getInventoryOperationDtos(List<InventoryOperationDto> add) {
        List<InventoryOperationDto> addData = new ArrayList<>();
        // 数据分组统计处理
        add.parallelStream()
                .collect(Collectors.groupingBy(InventoryOperationDto::getInventoryCode, Collectors.toList()))
                .forEach((id, transfer) -> {
                    transfer.stream()
                            .reduce((a, b) ->

                            {
                                InventoryOperationDto dto = new InventoryOperationDto();
                                dto.setUuid(IdUtil.simpleUUID());
                                dto.setInventoryCode(a.getInventoryCode());
                                dto.setQuantity(a.getQuantity().add(b.getQuantity()));
                                dto.setInventory(a.getInventory());
                                return dto;
                            })
                            .ifPresent(addData::add);
                });
        return addData;
    }

    private boolean judgeBigDecimalIsInteger(BigDecimal decimal) {
        BigDecimal decimal1 = decimal.setScale(6, BigDecimal.ROUND_HALF_UP);
        BigDecimal integer = decimal.setScale(0, BigDecimal.ROUND_HALF_UP);
        if (decimal1.compareTo(integer) == 0) {
            return true;
        } else {
            return false;
        }
    }

}
